using System;
using System.Web.Security;
using System.Collections.Generic;
using System.Configuration.Provider;
using System.Text;
using SubSonicMembershipProvider.DataObjects;
using SubSonic;

namespace SubSonicMembership
{
    public class SimpleMembershipProvider : MembershipProviderBase
    {
        #region Entity Methods
        protected override int GetApplicationId(string applicationName)
        {
            //this implementation doesn't use applications
            ApplicationId = 0;
            return 0;
        }
        protected UserMembership GetUserMembership(string userName)
        {
            
            UserMembership user = new UserMembership();
            user.LoadByParam("LoweredUserName", userName.ToLower());
            
            if(!user.IsLoaded)
                return null;
            return user;
        }
        
        protected MembershipUser MembershipUserFromUserMembership(UserMembership user)
        {
            MembershipUser m = new MembershipUser(this.Name,
                user.UserName,
                user.UserId,
                user.Email,
                user.PasswordQuestion,
                user.Comment,
                user.IsApproved,
                user.IsLockedOut,
                user.CreateDate,
                (DateTime)user.LastLoginDate,
                user.LastActivityDate,
                (DateTime)user.LastPasswordChangedDate,
                (DateTime)user.LastLockoutDate);

            return m;
        }
        #endregion

        //
        // System.Web.Security.MembershipProvider methods.
        //

        //
        // MembershipProvider.ChangePassword
        //

        public override bool ChangePassword(string username, string oldPwd, string newPwd)
        {
            if (!ValidateUser(username, oldPwd))
                return false;


            ValidatePasswordEventArgs args =
              new ValidatePasswordEventArgs(username, newPwd, true);

            OnValidatingPassword(args);

            if (args.Cancel)
                if (args.FailureInformation != null)
                    throw args.FailureInformation;
                else
                    throw new MembershipPasswordException("Change password canceled due to new password validation failure.");

            UserMembership member = GetUserMembership(username);
            member.Password = EncodePassword(newPwd);
            member.LastPasswordChangedDate = DateTime.Now;

            try
            {
                member.Save();
            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "ChangePassword");

                    throw new ProviderException(exceptionMessage);
                }
                else
                {
                    throw;
                }
            }
            return true;
        }

        //
        // MembershipProvider.ChangePasswordQuestionAndAnswer
        //

        public override bool ChangePasswordQuestionAndAnswer(string username,
                      string password,
                      string newPwdQuestion,
                      string newPwdAnswer)
        {
            if (!ValidateUser(username, password))
                return false;
            UserMembership user = GetUserMembership(username);
            user.PasswordAnswer = newPwdAnswer;
            user.PasswordQuestion = newPwdQuestion;

            try
            {
                user.Save();
            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "ChangePasswordQuestionAndAnswer");

                    throw new ProviderException(exceptionMessage);
                }
                else
                {
                    throw;
                }
            }
            return true;
        }



        //
        // MembershipProvider.CreateUser
        //

        public override MembershipUser CreateUser(string username,
                 string password,
                 string email,
                 string passwordQuestion,
                 string passwordAnswer,
                 bool isApproved,
                 object providerUserKey,
                 out MembershipCreateStatus status)
        {
            try
            {
                ValidatePasswordEventArgs args =
                  new ValidatePasswordEventArgs(username, password, true);

                OnValidatingPassword(args);

                if (args.Cancel)
                {
                    status = MembershipCreateStatus.InvalidPassword;
                    return null;
                }
                if (providerUserKey != null)
                {
                    status = MembershipCreateStatus.InvalidProviderUserKey;
                }
                if (RequiresUniqueEmail && GetUserNameByEmail(email) != "")
                {
                    status = MembershipCreateStatus.DuplicateEmail;
                    return null;
                }

                MembershipUser u = GetUser(username, false);

                if (u == null)
                {
                    DateTime createDate = DateTime.Now;
                    UserMembership user = new UserMembership();
                    user.UserName = username;
                    user.LoweredUserName = username.ToLower();
                    user.IsAnonymous = false;
                    user.LastActivityDate = createDate;
                    user.Password = EncodePassword(password);
                    user.PasswordFormat = (int)PasswordFormat;
                    user.PasswordSalt = GetPasswordSalt();
                    user.PasswordQuestion = passwordQuestion;
                    user.PasswordAnswer = EncodePassword(passwordAnswer);
                    user.Email = email;
                    user.IsApproved = isApproved;
                    user.CreateDate = createDate;
                    user.LastPasswordChangedDate = createDate;
                    user.IsLockedOut = false;
                    user.LastLockoutDate = createDate;
                    user.LastLoginDate = createDate;
                    user.FailedPasswordAttemptWindowStart = createDate;
                    user.FailedPasswordAnswerAttemptWindowStart = createDate;
                    user.Save();
                    status = MembershipCreateStatus.Success;
                    return GetUser(username, false);
                }
                else
                {
                    status = MembershipCreateStatus.DuplicateUserName;
                }
            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "CreateUser");
                }

                status = MembershipCreateStatus.ProviderError;
            }

            return null;
        }



        //
        // MembershipProvider.DeleteUser
        //

        public override bool DeleteUser(string username, bool deleteAllRelatedData)
        {
            UserMembership user = GetUserMembership(username);
            if (user == null | user.UserId == 0)
                return false;
            try
            {
                UserMembershipController uc = new UserMembershipController();
                uc.Delete(user.UserId);

                if (deleteAllRelatedData)
                {
                    // TODO: Process commands to delete all data for the user in the database.
                }
            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "DeleteUser");

                    throw new ProviderException(exceptionMessage);
                }
                else
                {
                    throw;
                }
            }
            return true;
        }



        //
        // MembershipProvider.GetAllUsers
        //

        public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
        {
            try
            {
                Query q = new Query(UserMembership.Schema);
                q.PageIndex = pageIndex;
                q.PageSize = pageSize;

                UserMembershipCollection users = new UserMembershipCollection();
                users.LoadAndCloseReader(q.ExecuteReader());

                
                totalRecords = users.Count;
                MembershipUserCollection members = new MembershipUserCollection();
                foreach (UserMembership user in users)
                {
                    members.Add(MembershipUserFromUserMembership(user));
                }
                //throw new Exception("test this paging, thoroughly");
                return members;
            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "GetAllUsers");

                    throw new ProviderException(exceptionMessage);
                }
                else
                {
                    throw;
                }
            }
        }


        //
        // MembershipProvider.GetNumberOfUsersOnline
        //

        public override int GetNumberOfUsersOnline()
        {
            try
            {

                TimeSpan onlineSpan = new TimeSpan(0, System.Web.Security.Membership.UserIsOnlineTimeWindow, 0);
                DateTime compareTime = DateTime.Now.Subtract(onlineSpan);
                
                UserMembershipCollection users = new UserMembershipCollection().Where(UserMembership.Columns.LastActivityDate, Comparison.GreaterThan, compareTime);
                Query q = new Query(UserMembership.Schema).WHERE(UserMembership.Columns.LastActivityDate, Comparison.GreaterThan, compareTime);
                //TODO: test this sucker
                decimal numOnline = (decimal) q.ExecuteScalar();
                return (int)numOnline;
            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "GetNumberOfUsersOnline");

                    throw new ProviderException(exceptionMessage);
                }
                else
                {
                    throw;
                }
            }
        }



        //
        // MembershipProvider.GetPassword
        //

        public override string GetPassword(string username, string answer)
        {
            try
            {
                if (!EnablePasswordRetrieval)
                {
                    throw new ProviderException("Password Retrieval Not Enabled.");
                }
                if (PasswordFormat == MembershipPasswordFormat.Hashed)
                {
                    throw new ProviderException("Cannot retrieve Hashed passwords.");
                }
                UserMembership user = GetUserMembership(username);
                if (user.IsLockedOut)
                {
                    throw new MembershipPasswordException("The supplied user is locked out.");
                }
                if (RequiresQuestionAndAnswer && !CheckPassword(answer, user.PasswordAnswer))
                {
                    UpdateFailureCount(username, "passwordAnswer");
                    throw new MembershipPasswordException("Incorrect password answer.");
                }
                if (PasswordFormat == MembershipPasswordFormat.Encrypted)
                {
                    return UnEncodePassword(user.Password);
                }
                return user.Password;
            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "GetPassword");

                    throw new ProviderException(exceptionMessage);
                }
                else
                {
                    throw;
                }
            }
        }



        //
        // MembershipProvider.GetUser(string, bool)
        //

        public override MembershipUser GetUser(string username, bool userIsOnline)
        {
            try
            {
                UserMembership user = GetUserMembership(username);
                if (user == null)
                    return null;
                if (userIsOnline)
                {
                    user.LastActivityDate = DateTime.Now;
                    user.Save();
                    //throw new Exception("test this update");
                }
                return MembershipUserFromUserMembership(user);

            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "GetUser(String, Boolean)");

                    throw new ProviderException(exceptionMessage);
                }
                else
                {
                    throw;
                }
            }
        }


        //
        // MembershipProvider.GetUser(object, bool)
        //

        public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
        {
            try
            {
                UserMembership user = new UserMembership((int)providerUserKey);
                if (userIsOnline)
                {
                    user.LastActivityDate = DateTime.Now;
                    user.Save();
                    //throw new Exception("test this update");
                }
                return MembershipUserFromUserMembership(user);

            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "GetUser(object, Boolean)");

                    throw new ProviderException(exceptionMessage);
                }
                else
                {
                    throw;
                }
            }
        }

        //
        // MembershipProvider.UnlockUser
        //

        public override bool UnlockUser(string username)
        {
            try
            {
                UserMembership user = GetUserMembership(username);
                user.IsLockedOut = false;
                user.LastLockoutDate = DateTime.Now;
                user.Save();
                return true;
            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "UnlockUser");

                    throw new ProviderException(exceptionMessage);
                }
                else
                {
                    throw;
                }
            }
        }


        //
        // MembershipProvider.GetUserNameByEmail
        //

        public override string GetUserNameByEmail(string email)
        {
            try
            {
                UserMembership user = new UserMembership(UserMembership.Columns.Email, email);
                return user.UserName;
            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "GetUserNameByEmail");

                    throw new ProviderException(exceptionMessage);
                }
                else
                {
                    throw;
                }
            }
        }

        //
        // MembershipProvider.ResetPassword
        //

        public override string ResetPassword(string username, string answer)
        {
            try
            {
                if (!EnablePasswordReset)
                {
                    throw new NotSupportedException("Password reset is not enabled.");
                }

                if (answer == null && RequiresQuestionAndAnswer)
                {
                    UpdateFailureCount(username, "passwordAnswer");

                    throw new ProviderException("Password answer required for password reset.");
                }

                string newPassword =
                  System.Web.Security.Membership.GeneratePassword(newPasswordLength, MinRequiredNonAlphanumericCharacters);


                ValidatePasswordEventArgs args =
                  new ValidatePasswordEventArgs(username, newPassword, true);

                OnValidatingPassword(args);

                if (args.Cancel)
                    if (args.FailureInformation != null)
                        throw args.FailureInformation;
                    else
                        throw new MembershipPasswordException("Reset password canceled due to password validation failure.");

                UserMembership user = GetUserMembership(username);

                if (user.IsLockedOut)
                    throw new MembershipPasswordException("The supplied user is locked out.");


                if (RequiresQuestionAndAnswer && !CheckPassword(answer, user.PasswordAnswer))
                {
                    UpdateFailureCount(username, "passwordAnswer");

                    throw new MembershipPasswordException("Incorrect password answer.");
                }
                user.Password = newPassword;
                user.LastPasswordChangedDate = DateTime.Now;
                user.Save();
                if (!user.IsDirty)
                    return newPassword;
                else
                    throw new MembershipPasswordException("User not found, or user is locked out. Password not Reset.");

            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "ResetPassword");

                    throw new ProviderException(exceptionMessage);
                }
                else
                {
                    throw;
                }
            }
        }


        //
        // MembershipProvider.UpdateUser
        //

        public override void UpdateUser(MembershipUser user)
        {
            try
            {
                UserMembership userEntity = GetUserMembership(user.UserName);
                userEntity.Email = user.Email;
                userEntity.Comment = user.Comment;
                userEntity.IsApproved = user.IsApproved;
                userEntity.Save();
            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "UpdateUser");

                    throw new ProviderException(exceptionMessage);
                }
                else
                {
                    throw;
                }
            }
        }


        //
        // MembershipProvider.ValidateUser
        //

        public override bool ValidateUser(string username, string password)
        {
            try
            {
                UserMembership user = GetUserMembership(username);
                if (user == null || user.IsLockedOut)
                    return false;
                if (CheckPassword(password, user.Password))
                {
                    if (user.IsApproved)
                    {
                        user.LastLoginDate = DateTime.Now;
                        user.Save();
                        return true;
                    }
                    else
                        return false;
                }
                else
                {
                    UpdateFailureCount(username, "password");
                    return false;
                }
            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "ValidateUser");
                    throw new ProviderException(exceptionMessage);
                }
                else
                {
                    throw;
                }
            }
        }


        //
        // UpdateFailureCount
        //   A helper method that performs the checks and updates associated with
        // password failure tracking.
        //

        private void UpdateFailureCount(string username, string failureType)
        {

            UserMembership user = GetUserMembership(username);
            DateTime windowStart = new DateTime();
            int failureCount = 0;

            try
            {

                if (failureType == "password")
                {
                    failureCount = (int) user.FailedPasswordAnswerAttemptCount;
                    windowStart = (DateTime) user.FailedPasswordAttemptWindowStart;
                }

                if (failureType == "passwordAnswer")
                {
                    failureCount = (int) user.FailedPasswordAnswerAttemptCount;
                    windowStart = (DateTime) user.FailedPasswordAnswerAttemptWindowStart;
                }

                DateTime windowEnd = windowStart.AddMinutes(PasswordAttemptWindow);

                if (failureCount == 0 || DateTime.Now > windowEnd)
                {
                    // First password failure or outside of PasswordAttemptWindow. 
                    // Start a new password failure count from 1 and a new window starting now.


                    if (failureType == "password")
                    {
                        user.FailedPasswordAttemptCount = 1;
                        user.FailedPasswordAttemptWindowStart = DateTime.Now;
                    }
                    if (failureType == "passwordAnswer")
                    {
                        user.FailedPasswordAnswerAttemptCount = 1;
                        user.FailedPasswordAnswerAttemptWindowStart = DateTime.Now;
                    }
                    user.Save();
                    if (user.IsDirty)
                        throw new ProviderException("Unable to update failure count and window start.");

                }
                else
                {
                    if (failureCount++ >= MaxInvalidPasswordAttempts)
                    {
                        // Password attempts have exceeded the failure threshold. Lock out
                        // the user.
                        user.IsLockedOut = true;
                        user.LastLockoutDate = DateTime.Now;
                        user.Save();
                        if (user.IsDirty)
                            throw new ProviderException("Unable to lock out user.");
                    }
                    else
                    {
                        // Password attempts have not exceeded the failure threshold. Update
                        // the failure counts. Leave the window the same.

                        if (failureType == "password")
                        {
                            user.FailedPasswordAttemptCount = failureCount;
                        }

                        if (failureType == "passwordAnswer")
                        {
                            user.FailedPasswordAnswerAttemptCount = failureCount;
                        }
                        user.Save();
                        if (user.IsDirty)
                            throw new ProviderException("Unable to update failure count.");
                    }
                }
            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "UpdateFailureCount");

                    throw new ProviderException(exceptionMessage);
                }
                else
                {
                    throw;
                }
            }
        }

        //
        // MembershipProvider.FindUsersByName
        //

        public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
        {
            try
            {
                Query q = new Query(UserMembership.Schema).WHERE(UserMembership.Columns.LoweredUserName, Comparison.Like, "%" + usernameToMatch + "%");
                q.PageIndex = pageIndex;
                q.PageSize = pageSize;
                UserMembershipCollection users = new UserMembershipCollection();
                users.LoadAndCloseReader(q.ExecuteReader());
                totalRecords = users.Count;

                MembershipUserCollection members = new MembershipUserCollection();
                foreach (UserMembership user in users)
                {
                    members.Add(MembershipUserFromUserMembership(user));

                }
                return members;
            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "FindUsersByName");

                    throw new ProviderException(exceptionMessage);
                }
                else
                {
                    throw;
                }
            }
        }

        //
        // MembershipProvider.FindUsersByEmail
        //

        public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords)
        {
            try
            {
                Query q = new Query(UserMembership.Schema).WHERE(UserMembership.Columns.LoweredUserName, Comparison.Like, "%" + emailToMatch + "%");
                q.PageIndex = pageIndex;
                q.PageSize = pageSize;
                UserMembershipCollection users = new UserMembershipCollection();
                users.LoadAndCloseReader(q.ExecuteReader());
                totalRecords = users.Count;

                MembershipUserCollection members = new MembershipUserCollection();
                foreach (UserMembership user in users)
                {
                    members.Add(MembershipUserFromUserMembership(user));

                }
                return members;
            }
            catch (Exception e)
            {
                if (WriteExceptionsToEventLog)
                {
                    WriteToEventLog(e, "FindUsersByEmail");

                    throw new ProviderException(exceptionMessage);
                }
                else
                {
                    throw;
                }
            }
        }
    }
}
